Cargar datos en cluster

Normalmente, este paso no lo hacemos en nuestra sesión de análisis: los datos están distribuidos en un cluster originalmente. Aunque podemos leer en este caso los datos a R y después copiarlos a Spark, esto en general es poco eficiente y a veces no es posible. Leeremos directamente al cluster.

Los datos completos están aquí

Iniciamos cluster un cluster local (en general nos conectamos a un cluster real):

library(tidyverse)
library(sparklyr)
#library(arrow)
config <- spark_config()
# configuración para modo local:
config$`sparklyr.shell.driver-memory` <- "2G" # para poder hacer collect de pares más adelante
sc <- spark_connect(master = "local", config = config)
* Using Spark: 2.4.0
options(scipen = 999)
ruta <- "../../datos/similitud/wiki-100000.txt"
articulos_tbl <- spark_read_csv(sc, 
  name = "articulos_df", 
  path = ruta, 
  columns = c("articulo", "categoria"),
  header =FALSE,
  delimiter = " ",
  repartition = 6) 
head(articulos_tbl)
articulos_tbl %>% tally()
articulos_tbl <- articulos_tbl %>% 
  #filter(rlike(line, "^[^#]")) %>% 
  group_by(articulo) %>% 
  summarise(categorias = collect_list(categoria))
articulos_tbl %>% sdf_sample(0.001)

Y binarizamos (la representación para usar la implementación de spark es de matriz rala: 1 cuando el token/shingle pertenece al documento, y 0 si no):

art_bin <- articulos_tbl %>% 
        ft_count_vectorizer('categorias', 'vector_cat', binary = TRUE) 
art_bin %>% sdf_sample(0.001)

Ahora definimos el número de hashes

# estimator
lsh_wiki_estimator <- ft_minhash_lsh(sc, "vector_cat", "hashes", 
                           seed = 1227,
                           num_hash_tables = 5)
lsh_wiki_trans <-  ml_fit(lsh_wiki_estimator, art_bin)
art_bin <- ml_transform(lsh_wiki_trans, art_bin)
art_bin %>% head(5)

Podemos encontrar vecinos cercanos

vec_1 <- art_bin %>% filter(articulo =='Alabama') %>% pull(vector_cat)
similares <- ml_approx_nearest_neighbors(lsh_wiki_trans, 
              art_bin, vec_1[[1]], num_nearest_neighbors = 10) %>% 
              select(articulo, categorias, distCol)
print(similares %>% collect)

Encontramos pares similares con un similarity join, por ejemplo:

art_bin <- art_bin %>% mutate(id = articulo)
pares_candidatos <- ml_approx_similarity_join(lsh_wiki_trans, art_bin, art_bin, 0.7,
  dist_col = "distCol") %>% filter(id_a != id_b)
pares_candidatos  %>% tally()
pares <- pares_candidatos %>% filter(distCol < 0.2)
pares %>% tally
pares <- pares %>% collect()

Por ejemplo

Nota: la implementación en spark de LSH utiliza solamente amplificación OR. Es posible usar suficientes hashes para obtener pares, y después filtrar los de la distancia que buscamos (¿Cómo implementar familias AND-OR)?

LS0tCnRpdGxlOiAiTFNIIHBhcmEgY2F0ZWdvcsOtYXMgZGUgV2lraXBlZGlhIGVuIFNwYXJrIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKIyMgQ2FyZ2FyIGRhdG9zIGVuIGNsdXN0ZXIKCk5vcm1hbG1lbnRlLCBlc3RlIHBhc28gbm8gbG8gaGFjZW1vcyBlbiBudWVzdHJhIHNlc2nDs24gZGUgYW7DoWxpc2lzOiBsb3MgZGF0b3MKZXN0w6FuIGRpc3RyaWJ1aWRvcyBlbiB1biBjbHVzdGVyIG9yaWdpbmFsbWVudGUuIEF1bnF1ZSBwb2RlbW9zIGxlZXIgZW4gZXN0ZSBjYXNvCmxvcyBkYXRvcyBhIFIgeSBkZXNwdcOpcyBjb3BpYXJsb3MgYSBTcGFyaywgZXN0byBlbiBnZW5lcmFsIGVzIHBvY28gZWZpY2llbnRlCnkgYSB2ZWNlcyBubyBlcyBwb3NpYmxlLiBMZWVyZW1vcyBkaXJlY3RhbWVudGUgYWwgY2x1c3Rlci4KCkxvcyBkYXRvcyBjb21wbGV0b3MgZXN0w6FuIFthcXXDrV0oaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3dpa2ktbGFyZ2UvYXJ0aWNsZV9jYXRlZ29yaWVzX2VuLnR0bCkKCkluaWNpYW1vcyBjbHVzdGVyIHVuIGNsdXN0ZXIgbG9jYWwgKGVuIGdlbmVyYWwgbm9zIGNvbmVjdGFtb3MgYSB1biBjbHVzdGVyIHJlYWwpOgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNwYXJrbHlyKQojbGlicmFyeShhcnJvdykKY29uZmlnIDwtIHNwYXJrX2NvbmZpZygpCiMgY29uZmlndXJhY2nDs24gcGFyYSBtb2RvIGxvY2FsOgpjb25maWckYHNwYXJrbHlyLnNoZWxsLmRyaXZlci1tZW1vcnlgIDwtICIyRyIgIyBwYXJhIHBvZGVyIGhhY2VyIGNvbGxlY3QgZGUgcGFyZXMgbcOhcyBhZGVsYW50ZQpgYGAKCgpgYGB7cn0Kc2MgPC0gc3BhcmtfY29ubmVjdChtYXN0ZXIgPSAibG9jYWwiLCBjb25maWcgPSBjb25maWcpCmBgYAoKCmBgYHtyfQpvcHRpb25zKHNjaXBlbiA9IDk5OSkKcnV0YSA8LSAiLi4vLi4vZGF0b3Mvc2ltaWxpdHVkL3dpa2ktMTAwMDAwLnR4dCIKYXJ0aWN1bG9zX3RibCA8LSBzcGFya19yZWFkX2NzdihzYywgCiAgbmFtZSA9ICJhcnRpY3Vsb3NfZGYiLCAKICBwYXRoID0gcnV0YSwgCiAgY29sdW1ucyA9IGMoImFydGljdWxvIiwgImNhdGVnb3JpYSIpLAogIGhlYWRlciA9RkFMU0UsCiAgZGVsaW1pdGVyID0gIiAiLAogIHJlcGFydGl0aW9uID0gNikgCmhlYWQoYXJ0aWN1bG9zX3RibCkKYXJ0aWN1bG9zX3RibCAlPiUgdGFsbHkoKQpgYGAKCgpgYGB7cn0KYXJ0aWN1bG9zX3RibCA8LSBhcnRpY3Vsb3NfdGJsICU+JSAKICAjZmlsdGVyKHJsaWtlKGxpbmUsICJeW14jXSIpKSAlPiUgCiAgZ3JvdXBfYnkoYXJ0aWN1bG8pICU+JSAKICBzdW1tYXJpc2UoY2F0ZWdvcmlhcyA9IGNvbGxlY3RfbGlzdChjYXRlZ29yaWEpKQphcnRpY3Vsb3NfdGJsICU+JSBzZGZfc2FtcGxlKDAuMDAxKQpgYGAKClkgYmluYXJpemFtb3MgKGxhIHJlcHJlc2VudGFjacOzbiBwYXJhIHVzYXIgbGEgaW1wbGVtZW50YWNpw7NuIGRlIHNwYXJrIGVzCmRlIG1hdHJpeiByYWxhOiAxIGN1YW5kbyBlbCB0b2tlbi9zaGluZ2xlIHBlcnRlbmVjZSBhbCBkb2N1bWVudG8sIHkgMCBzaSBubyk6CgpgYGB7cn0KYXJ0X2JpbiA8LSBhcnRpY3Vsb3NfdGJsICU+JSAKICAgICAgICBmdF9jb3VudF92ZWN0b3JpemVyKCdjYXRlZ29yaWFzJywgJ3ZlY3Rvcl9jYXQnLCBiaW5hcnkgPSBUUlVFKSAKYXJ0X2JpbiAlPiUgc2RmX3NhbXBsZSgwLjAwMSkKYGBgCgoKQWhvcmEgZGVmaW5pbW9zIGVsIG7Dum1lcm8gZGUgaGFzaGVzCgpgYGB7cn0KIyBlc3RpbWF0b3IKbHNoX3dpa2lfZXN0aW1hdG9yIDwtIGZ0X21pbmhhc2hfbHNoKHNjLCAidmVjdG9yX2NhdCIsICJoYXNoZXMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDEyMjcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9oYXNoX3RhYmxlcyA9IDUpCmBgYAoKYGBge3J9CmxzaF93aWtpX3RyYW5zIDwtICBtbF9maXQobHNoX3dpa2lfZXN0aW1hdG9yLCBhcnRfYmluKQphcnRfYmluIDwtIG1sX3RyYW5zZm9ybShsc2hfd2lraV90cmFucywgYXJ0X2JpbikKYXJ0X2JpbiAlPiUgaGVhZCg1KQpgYGAKClBvZGVtb3MgZW5jb250cmFyIHZlY2lub3MgY2VyY2Fub3MKYGBge3J9CnZlY18xIDwtIGFydF9iaW4gJT4lIGZpbHRlcihhcnRpY3VsbyA9PSdBbGFiYW1hJykgJT4lIHB1bGwodmVjdG9yX2NhdCkKc2ltaWxhcmVzIDwtIG1sX2FwcHJveF9uZWFyZXN0X25laWdoYm9ycyhsc2hfd2lraV90cmFucywgCiAgICAgICAgICAgICAgYXJ0X2JpbiwgdmVjXzFbWzFdXSwgbnVtX25lYXJlc3RfbmVpZ2hib3JzID0gMTApICU+JSAKICAgICAgICAgICAgICBzZWxlY3QoYXJ0aWN1bG8sIGNhdGVnb3JpYXMsIGRpc3RDb2wpCnByaW50KHNpbWlsYXJlcyAlPiUgY29sbGVjdCkKYGBgCgpFbmNvbnRyYW1vcyBwYXJlcyBzaW1pbGFyZXMgY29uIHVuICpzaW1pbGFyaXR5IGpvaW4qLCBwb3IgZWplbXBsbzoKCmBgYHtyfQphcnRfYmluIDwtIGFydF9iaW4gJT4lIG11dGF0ZShpZCA9IGFydGljdWxvKQpwYXJlc19jYW5kaWRhdG9zIDwtIG1sX2FwcHJveF9zaW1pbGFyaXR5X2pvaW4obHNoX3dpa2lfdHJhbnMsIGFydF9iaW4sIGFydF9iaW4sIDAuNywKICBkaXN0X2NvbCA9ICJkaXN0Q29sIikgJT4lIGZpbHRlcihpZF9hICE9IGlkX2IpCnBhcmVzX2NhbmRpZGF0b3MgICU+JSB0YWxseSgpCmBgYAoKYGBge3J9CnBhcmVzIDwtIHBhcmVzX2NhbmRpZGF0b3MgJT4lIGZpbHRlcihkaXN0Q29sIDwgMC4yKQpwYXJlcyAlPiUgdGFsbHkKcGFyZXMgPC0gcGFyZXMgJT4lIGNvbGxlY3QoKQpgYGAKClBvciBlamVtcGxvCgpgYGB7cn0KRFQ6OmRhdGF0YWJsZShwYXJlcyAlPiUgZmlsdGVyKHN0cl9kZXRlY3QoaWRfYSwgInBva2VyIikgfCBzdHJfZGV0ZWN0KGlkX2IsICJwb2tlciIpKSkKYGBgCgoKCk5vdGE6IGxhIGltcGxlbWVudGFjacOzbiBlbiBzcGFyayBkZSBMU0ggdXRpbGl6YSBzb2xhbWVudGUgYW1wbGlmaWNhY2nDs24gT1IuIApFcyBwb3NpYmxlIHVzYXIgc3VmaWNpZW50ZXMgaGFzaGVzIHBhcmEgb2J0ZW5lciBwYXJlcywgeSBkZXNwdcOpcyBmaWx0cmFyCmxvcyBkZSBsYSBkaXN0YW5jaWEgcXVlIGJ1c2NhbW9zICjCv0PDs21vIGltcGxlbWVudGFyIGZhbWlsaWFzIEFORC1PUik/Cgo=